#include <iostream>
#include <Eigen/Dense>

#include <QApplication>
#include <QDebug>
#include <QTime>

#include "matrix.h"
#include "matrixsheet.h"
#include "matrixbook.h"
#include "delegates.h"
#include "testmatrixsheet.h"

static void test1()
{
    using namespace Eigen;
    MatrixXd m(2,5);
    m.resize(4,3);
    qDebug() << "The matrix m is of size "
             << m.rows() << "x" << m.cols();
    qDebug() << "It has " << m.size() << " coefficients";
    VectorXd v(2);
    v.resize(5);
    qDebug() << "The vector v is of size " << v.size();
    qDebug() << "As a matrix, v is of size "
             << v.rows() << "x" << v.cols();
}

static void test2()
{
    using namespace Eigen;
    MatrixXd m(2,2);
    m(0,0) = 3;
    m(1,0) = 2.5;
    m(0,1) = -1;
    m(1,1) = m(1,0) + m(0,1);
    qDebug() << "Here is the matrix m:\n" << m.cols() << " x " << m.rows();
    VectorXd v(2);
    v(0) = 4;
    v(1) = v(0) - 1;
    qDebug() << "Here is the vector v:\n" << v.cols();
}

static void matrix_assign_op()
{
    using namespace Eigen;
    Matrix3f m;
    m << 1, 2, 3,
            4, 5, 6,
            7, 8, 9;
    qDebug() << "cols: " << m.cols() << " rows: " << m.rows();
    std::cout << m.transpose();
}


static void matrix_mani()
{
    using namespace Eigen;
    MatrixXcf a = MatrixXcf::Random(2,2);
    std::cout << "Here is the matrix a\n" << a << std::endl;
    std::cout << "Here is the matrix a^T\n" << a.transpose() << std::endl;
    std::cout << "Here is the conjugate of a\n" << a.conjugate() << std::endl;
    std::cout << "Here is the matrix a^*\n" << a.adjoint() << std::endl;
}

static void testHouseHolder()
{
    using namespace Eigen;
    Matrix3d A;
    A << 1, 1, 1, 2, -1, -1, 2, -4, 5;
    HouseholderQR<Matrix3d> qr;
    qr.compute(A);
    MatrixXd R = qr.matrixQR().triangularView<Upper>();
    MatrixXd Q =  qr.householderQ();
    std::cout << "QR2(): HouseholderQR---------------------------------------------"<< std::endl;
    std::cout << "A "<< std::endl <<A << std::endl << std::endl;
    std::cout <<"qr.matrixQR()"<< std::endl << qr.matrixQR() << std::endl << std::endl;
    std::cout << "R"<< std::endl <<R << std::endl << std::endl;
    std::cout << "Q "<< std::endl <<Q << std::endl << std::endl;
    std::cout <<"Q*R" << std::endl <<Q*R << std::endl << std::endl;
}

static void testSimVizMatrix()
{
    qsrand(QTime::currentTime().second());
    IntMatrix intM(3, 3);
    IntMatrix intM2(3, 3);
    FloatMatrix floatM(3, 3);
    for (int i = 0; i < 3; ++i) {
        for (int j = 0; j < 3; ++j) {
            intM.setValue(i, j, i + 1);
            intM2.setValue(i, j, i + 1);
            floatM.setValue(i, j, (float)i / 2);
        }
    }


    qDebug() << "matrix 1:";
    qDebug() << intM;
    qDebug() << "transpose intM:";
    intM.transpose();
    qDebug() << intM;

    qDebug() << "matrix 2:";
    qDebug() << intM2;
    qDebug() << "float matrix:";
    qDebug() << floatM;
    qDebug() << "substraction:";
    qDebug() << intM - intM2;
    qDebug() << "addition:";
    qDebug() << intM + intM2;
    qDebug() << "multiple 3:";
    qDebug() << intM * 3;
    qDebug() << "float matrix * 10:";
    qDebug() << floatM * 10;

//    intM.transpose();
//    intM.adjoint();
//    intM.conjugate();
    qDebug() << intM;
    qDebug() << "column 1 data:";
    qDebug() << intM.column(1);
    qDebug() << "row 2 data: ";
    qDebug() << intM.row(2);
    qDebug() << "sum: " << intM.sum();
    qDebug() << "product: " << intM.product();
    qDebug() << "mean: " << intM.mean();
    qDebug() << "minCoeff: " << intM.minCoeff();
    qDebug() << "maxCoeff: " << intM.maxCoeff();
}

//static int testMatrixSheet(int argc, char* argv[])
//{
//    QApplication app(argc, argv);
//    MatrixBase* matrix = new FloatMatrix(100, 100);
//    matrix->random();

//    MatrixSheet sheet;
//    sheet.setMatrix(matrix);
//    sheet.show();
//    return app.exec();
//}

static int testMatrixBook(int argc, char* argv[])
{
    QApplication app(argc, argv);

    const int ColumnCount = 50;
    const int RowCount = 50;
    MatrixBook book;

    QVector<double> horHeaderData(ColumnCount);
    QVector<double> verHeaderData(RowCount);

    auto stuffRandomData = [] (QVector<double>& vector, int cnt) {
        for (int i = 0; i < cnt; ++i)
            vector[i] = qrand() % 1000;
    };

    stuffRandomData(horHeaderData, ColumnCount);
    stuffRandomData(verHeaderData, RowCount);

    ///
    QSharedPointer<MatrixBase> matrix1(new FloatMatrix(RowCount, ColumnCount));
    matrix1->random();

    MatrixSheet* sheet1 = new MatrixSheet(&book);
    sheet1->setMatrix(matrix1);
    sheet1->setWindowTitle("Float");

    ///
    QSharedPointer<MatrixBase> matrix2(new IntMatrix(RowCount, ColumnCount));
    matrix2->random();

    MatrixSheet* sheet2 = new MatrixSheet(&book);
    sheet2->setMatrix(matrix2);
    sheet2->setWindowTitle("Int");
    sheet2->setHorizontalHeaderData(horHeaderData);
    sheet2->setVerticalHeaderData(verHeaderData);

////    ///
    QSharedPointer<MatrixBase> matrix3(new DoubleMatrix(RowCount, ColumnCount));
    matrix3->random();

    MatrixSheet* sheet3 = new MatrixSheet(&book);
    sheet3->setMatrix(matrix3);
    sheet3->setWindowTitle("Double");
    sheet3->show();

    ///
    QSharedPointer<MatrixBase> matrix4(new ComplexFloatMatrix(RowCount, ColumnCount));
    matrix4->random();

    MatrixSheet* sheet4 = new MatrixSheet(&book);
    sheet4->setMatrix(matrix4);
    sheet4->setWindowTitle("Complex Float");

    ///
    QSharedPointer<MatrixBase> matrix5(new ComplexDoubleMatrix(RowCount, ColumnCount));
    matrix5->random();

    MatrixSheet* sheet5 = new MatrixSheet(&book);
    sheet5->setMatrix(matrix5);
    sheet5->setWindowTitle("Complex Double");

    book.addSheet(sheet1);
    book.addSheet(sheet2);
    book.addSheet(sheet3);
    book.addSheet(sheet4);
    book.addSheet(sheet5);

    book.show();
    return app.exec();
}

static int testMatrixSheet(int argc, char* argv[])
{
    QApplication app(argc, argv);
    TestMatrixSheet win;
    win.show();
    return app.exec();
}

int main(int argc, char* argv[])
{
    //    test1();
    //    QApplication app(argc, argv);
    //    ComDataGui::MatrixBook book;
    //    book.show();
    //    return app.exec();
//    test1();
//    test2();
//    matrix_assign_op();
//    matrix_mani();
//    testSimVizMatrix();
//    return testMatrixSheet(argc, argv);
//    return testMatrixBook(argc, argv);
    return testMatrixSheet(argc ,argv);
}
